﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using MugenInjection.Delegates;
using MugenInjection.Interface;

namespace MugenInjection.Infrastructure
{
    /// <summary>
    ///     Represents the reflection access provider which use System.Linq.Expressions to members access.
    /// </summary>
    public sealed class ExpressionReflectionAccessProvider : IReflectionAccessProvider
    {
        #region Fields

        private static readonly CacheCollection<ConstructorInfo, ActivatorDelegate> ConstructorActivators =
            new CacheCollection<ConstructorInfo, ActivatorDelegate>(40);

        private static readonly CacheCollection<MemberInfo, Delegate> MemberActivators =
            new CacheCollection<MemberInfo, Delegate>(40);

        private static readonly CacheCollection<MemberInfo, Delegate> MemberActivatorsRef =
            new CacheCollection<MemberInfo, Delegate>(40);

        private static readonly CacheCollection<MemberInfo, Delegate> MemberGetters =
            new CacheCollection<MemberInfo, Delegate>(40);

        private static readonly CacheCollection<MemberInfo, Delegate> MethodActivators =
            new CacheCollection<MemberInfo, Delegate>(40);

        private static readonly CacheCollection<MemberInfo, Delegate> MethodActivatorsRef =
            new CacheCollection<MemberInfo, Delegate>(40);

        #endregion

        #region Implementation of IReflectionAccessProvider

        /// <summary>
        ///     Gets a delegate to create an object.
        /// </summary>
        /// <param name="constructorInfo">
        ///     The specified <see cref="ConstructorInfo" />.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="ActivatorDelegate" />
        /// </returns>
        public ActivatorDelegate GetActivatorDelegate(ConstructorInfo constructorInfo)
        {
            Validate.ArgumentNotNull(constructorInfo, "constructorInfo");
            return GetActivatorDelegate(constructorInfo, true);
        }

        /// <summary>
        ///     Gets a delegate to set a value in the specified <see cref="MemberInfo" />.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="SetterDelegate{TTarget}" />
        /// </returns>
        public SetterDelegate<TTarget> GetAssignMemberDelegate<TTarget>(MemberInfo memberInfo) where TTarget : class
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            return GetAssignMemberDelegate<SetterDelegate<TTarget>, TTarget>(memberInfo, false, true);
        }

        /// <summary>
        ///     Gets a delegate to set specified value in the specified <see cref="MemberInfo" /> in a value type target, can be used with reference type.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="SetterDelegate{TTarget}" />
        /// </returns>
        public SetterValueTypeDelegate<TTarget> GetAssignValueMemberDelegate<TTarget>(MemberInfo memberInfo)
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            return GetAssignMemberDelegate<SetterValueTypeDelegate<TTarget>, TTarget>(memberInfo, true, true);
        }

        /// <summary>
        ///     Gets a delegate to get a value in the specified <see cref="MemberInfo" />
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <typeparam name="T">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        public GetterDelegate<TTarget, T> GetGetterMemberDelegate<TTarget, T>(MemberInfo memberInfo)
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            lock (MemberGetters)
            {
                var memberGetter = MemberGetters[memberInfo] as GetterDelegate<TTarget, T>;
                if (memberGetter == null)
                {
#if NETFX_CORE
                    if (memberInfo.GetMemberType() == MemberTypes.Field)
#else
                    if (memberInfo.MemberType == MemberTypes.Field)
#endif

                        memberGetter = CreateFieldGetter<TTarget, GetterDelegate<TTarget, T>>(
                            (FieldInfo) memberInfo, typeof (TTarget), typeof (T));
                    else
                        memberGetter =
                            CreatePropertyGetter<TTarget, GetterDelegate<TTarget, T>>((PropertyInfo) memberInfo,
                                                                                      typeof (TTarget), typeof (T));
                    MemberGetters[memberInfo] = memberGetter;
                }
                return memberGetter;
            }
        }

        /// <summary>
        ///     Gets a delegate to call specified <see cref="MethodInfo" />.
        /// </summary>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <returns>
        ///     An instance of <see cref="InvokeMethodDelegate{TTarget}" />
        /// </returns>
        public InvokeMethodDelegate<TTarget> GetInvokeMethodDelegate<TTarget>(MethodInfo methodInfo)
            where TTarget : class
        {
            Validate.ArgumentNotNull(methodInfo, "methodInfo");
            return GetInvokeMethodDelegate<InvokeMethodDelegate<TTarget>, TTarget>(methodInfo, false, true);
        }

        /// <summary>
        ///     Gets a delegate to call specified <see cref="MethodInfo" /> in a value type target, can be used with reference type.
        /// </summary>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <returns>
        ///     An instance of <see cref="InvokeMethodDelegate{TTarget}" />
        /// </returns>
        public InvokeMethodValueTypeDelegate<TTarget> GetInvokeMethodValueTypeDelegate<TTarget>(MethodInfo methodInfo)
        {
            Validate.ArgumentNotNull(methodInfo, "methodInfo");
            return GetInvokeMethodDelegate<InvokeMethodValueTypeDelegate<TTarget>, TTarget>(methodInfo, true, true);
        }

        /// <summary>
        ///     Converts the Func{object[], object} to the specified delegate type.
        /// </summary>
        /// <param name="proxyDelegate">The specified proxy method.</param>
        /// <param name="delegateType">The specified delegate type.</param>
        /// <returns>An instance of delegate type.</returns>
        public Delegate ConvertToProxyDelegate(Func<object[], object> proxyDelegate, Type delegateType)
        {
            Validate.ArgumentNotNull(proxyDelegate, "proxyDelegate");
            Validate.ArgumentNotNull(delegateType, "delegateType");
#if !NETFX_CORE
            if (UseEmitToConvertDelegate)
                return EmitReflectionAccessProvider.ConvertToProxyDelegate(delegateType)
                                               .CreateDelegate(delegateType, proxyDelegate);
#endif
            Validate.IsAssignable(typeof(Delegate), delegateType);
            return ConvertToProxyDelegate(delegateType, proxyDelegate);
        }

        /// <summary>
        ///     Returns a weak-reference version of a delegate.
        /// </summary>
        /// <param name="originalAction">The delegate to convert to weak referencing.</param>
        /// <param name="unsubcribeDelegate">The unregister action to invoke if the target is garbage collected.</param>
        /// <returns>A weak referencing delegate.</returns>
        public Delegate ConvertToWeakDelegate(Delegate originalAction, Action<Delegate> unsubcribeDelegate)
        {
            Validate.ArgumentNotNull(originalAction, "originalAction");
            if (originalAction.Target == null || GetWeakEventInfo(originalAction) != null)
                return originalAction;

            var weakEventInfo = new WeakEventInfo
                                    {
                                        Target = new WeakReference(originalAction.Target),
#if NETFX_CORE
                                        OriginalMethod = originalAction.GetMethodInfo()
#else
                                        OriginalMethod = originalAction.Method
#endif
                                    };
            Delegate result = ConvertToWeakDelegate(weakEventInfo, originalAction);
            if (unsubcribeDelegate != null)
                weakEventInfo.UnsubcribeDelegate = unsubcribeDelegate;
            if (unsubcribeDelegate != null)
                weakEventInfo.Delegate = result;
            return result;
        }

        /// <summary>
        /// Finds the weak-delegate use a original delegate in the specified array of delegate.
        /// </summary>
        /// <param name="originalAction">The original delegate to find weak delegate.</param>
        /// <param name="delegates">The specified array of delegate</param>
        /// <returns>An instance of delegate.</returns>
        public Delegate FindWeakDelegateByOriginalDelegate(Delegate originalAction, IList<Delegate> delegates)
        {
            Validate.ArgumentNotNull(originalAction, "originalAction");
            Validate.ArgumentNotNull(delegates, "delegates");
#if NETFX_CORE
            var methodInfo = originalAction.GetMethodInfo();
#else
            var methodInfo = originalAction.Method;
#endif
            foreach (var @delegate in delegates)
            {
                var weakEventInfo = GetWeakEventInfo(@delegate);
                if (weakEventInfo == null) continue;

                if (weakEventInfo.Target.Target == originalAction.Target &&
                    Equals(weakEventInfo.OriginalMethod, methodInfo))
                    return @delegate;
            }
            return null;
        }

        #endregion

        #region Properties

#if !NETFX_CORE
        /// <summary>
        /// Gets or sets value that indicates how provider will create ProxyDelegate, if <c>true</c> - use emit generation; otherwise <c>false</c>
        /// </summary>
        public bool UseEmitToConvertDelegate { get; set; }
#endif
        #endregion

        #region Methods

        /// <summary>
        ///     Gets a delegate to create an object.
        /// </summary>
        /// <param name="constructorInfo">
        ///     The specified <see cref="ConstructorInfo" />.
        /// </param>
        /// <param name="useCache">
        ///     If <c>true</c> using cache;otherwise <c>false</c>.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="Func{TResult,TValue}" />
        /// </returns>
        internal static ActivatorDelegate GetActivatorDelegate(ConstructorInfo constructorInfo, bool useCache)
        {
            if (!useCache)
                return CreateActivatorMethod(constructorInfo);
            lock (ConstructorActivators)
            {
                var activator = ConstructorActivators[constructorInfo];
                if (activator == null)
                {
                    activator = CreateActivatorMethod(constructorInfo);
                    ConstructorActivators[constructorInfo] = activator;
                }
                return activator;
            }
        }

        /// <summary>
        ///     Gets a delegate to set a value in the specified <see cref="MemberInfo" />.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <typeparam name="TDelegate">Type of the delegate to create.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <param name="isValueType">
        ///     If <c>true</c> is value type; otherwise <c>false</c>.
        /// </param>
        /// <param name="useCache">
        ///     If <c>true</c> using cache;otherwise <c>false</c>.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="SetterDelegate{TTarget}" />
        /// </returns>
        internal static TDelegate GetAssignMemberDelegate<TDelegate, TTarget>(MemberInfo memberInfo, bool isValueType,
                                                                              bool useCache) where TDelegate : class
        {
            if (!useCache)
            {
#if NETFX_CORE
                if (memberInfo.GetMemberType() == MemberTypes.Field)
#else
                if (memberInfo.MemberType == MemberTypes.Field)
#endif
                {
                    if (isValueType)
                        return
                            CreateFieldSetter<TTarget, TDelegate>((FieldInfo)memberInfo,
                                                                  typeof(TTarget).MakeByRefType());
                    return
                        CreateFieldSetter<TTarget, TDelegate>((FieldInfo)memberInfo, typeof(TTarget));
                }
                if (isValueType)
                    return
                        CreatePropertySetter<TTarget, TDelegate>((PropertyInfo)memberInfo,
                                                                 typeof(TTarget).MakeByRefType());
                return
                    CreatePropertySetter<TTarget, TDelegate>((PropertyInfo)memberInfo, typeof(TTarget));
            }

            CacheCollection<MemberInfo, Delegate> cache = isValueType ? MemberActivatorsRef : MemberActivators;
            lock (cache)
            {
                var @delegate = cache[memberInfo] as TDelegate;
                if (@delegate == null)
                {
#if NETFX_CORE
                    if (memberInfo.GetMemberType() == MemberTypes.Field)
#else
                    if (memberInfo.MemberType == MemberTypes.Field)
#endif
                    {
                        if (isValueType)
                            @delegate =
                                CreateFieldSetter<TTarget, TDelegate>((FieldInfo)memberInfo,
                                                                      typeof(TTarget)
                                                                          .MakeByRefType());
                        else
                            @delegate =
                                CreateFieldSetter<TTarget, TDelegate>((FieldInfo)memberInfo,
                                                                      typeof(TTarget));
                    }
                    else
                    {
                        if (isValueType)
                            @delegate =
                                CreatePropertySetter<TTarget, TDelegate>((PropertyInfo)memberInfo,
                                                                         typeof(TTarget).MakeByRefType());
                        else
                            @delegate =
                                CreatePropertySetter<TTarget, TDelegate>((PropertyInfo)memberInfo,
                                                                         typeof(TTarget));
                    }
                    cache[memberInfo] = @delegate as Delegate;
                }
                return @delegate;
            }
        }

        /// <summary>
        ///     Gets a delegate to call specified <see cref="MethodInfo" />.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <typeparam name="TDelegate">Type of the delegate to create.</typeparam>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <param name="isValueType">
        ///     If <c>true</c> is value type; otherwise <c>false</c>.
        /// </param>
        /// <param name="useCache">
        ///     If <c>true</c> using cache;otherwise <c>false</c>.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="InvokeMethodDelegate{TTarget}" />
        /// </returns>
        internal static TDelegate GetInvokeMethodDelegate<TDelegate, TTarget>(MethodInfo methodInfo,
                                                                              bool isValueType, bool useCache)
            where TDelegate : class
        {
            if (!useCache)
            {
                if (isValueType)
                    return CreateMethodInvoke<TTarget, TDelegate>(methodInfo, typeof(TTarget).MakeByRefType());
                return CreateMethodInvoke<TTarget, TDelegate>(methodInfo, typeof(TTarget));
            }

            CacheCollection<MemberInfo, Delegate> cache = isValueType
                                                              ? MethodActivatorsRef
                                                              : MethodActivators;
            lock (cache)
            {
                var @delegate = cache[methodInfo] as TDelegate;
                if (@delegate == null)
                {
                    if (isValueType)
                        @delegate = CreateMethodInvoke<TTarget, TDelegate>(methodInfo,
                                                                           typeof(TTarget).MakeByRefType());
                    else
                        @delegate = CreateMethodInvoke<TTarget, TDelegate>(methodInfo, typeof(TTarget));
                    cache[methodInfo] = @delegate as Delegate;
                }
                return @delegate;
            }
        }

        /// <summary>
        ///     Returns a weak-reference version of a delegate.
        /// </summary>
        internal static Delegate ConvertToWeakDelegate(WeakEventInfo weakEventInfo, Delegate method)
        {
            var methodInfo = weakEventInfo.OriginalMethod;
            Type targetType = method.Target.GetType();
            LabelTarget resultLabel = Expression.Label();
            ConstantExpression constantExpression = Expression.Constant(weakEventInfo);
            ParameterExpression resultVariable = Expression.Variable(methodInfo.ReturnType == typeof(void)
                                                                         ? typeof(object)
                                                                         : methodInfo.ReturnType);
            Expression.Assign(resultVariable, Expression.Default(resultVariable.Type));
            ParameterExpression localField = Expression.Variable(targetType);
            ParameterExpression[] methodCallParameters = methodInfo.GetParameters()
                                                                   .Select(
                                                                       info => Expression.Parameter(info.ParameterType))
                                                                   .ToArray();

            Expression methodCallExpression = Expression.Call(localField, methodInfo, methodCallParameters);
            if (methodInfo.ReturnType != typeof(void))
                methodCallExpression = Expression.Assign(resultVariable, methodCallExpression);
            MemberExpression weakReferenceField = Expression.Field(constantExpression, WeakEventInfo.TargetFieldInfo);

            BlockExpression body = Expression.Block
                (new[] { resultVariable },
                 Expression.IfThenElse(Expression.Equal(weakReferenceField, Expression.Constant(null)),
                                       Expression.Return(resultLabel),
                //if (weakEventInfo.Target == null)
                //   return null;
                //else
                                       Expression.Block
                                           (
                                               new[] { localField },
                                               Expression.Assign(localField,
                                                                 Expression.Convert(
                                                                     Expression.Property(weakReferenceField,
                                                                                         typeof(WeakReference)
                                                                                             .GetProperty("Target")),
                                                                     targetType)),
                //var target = weakEventInfo.OriginalDelegate.Target;
                //if (target == null){
                                               Expression.IfThenElse(
                                                   Expression.Equal(localField, Expression.Constant(null)),
                                                   Expression.Block
                                                       (
                                                           Expression.Call(constantExpression,
                                                                           WeakEventInfo.ClearMethodInfo),
                                                           Expression.Return(resultLabel)
                                                       ),
                //weakEventInfo.Clear();
                //return null;
                //}
                //else
                //return target.Invoke(arg1, arg2, .....);
                                                   Expression.Block(methodCallExpression, Expression.Return(resultLabel))))),
                 Expression.Label(resultLabel), resultVariable);


            return Expression.Lambda(method.GetType(), body, methodCallParameters).Compile();
        }

        /// <summary>
        ///     Converts the Func{object[], object} to the specified delegate type.
        /// </summary>
        /// <param name="delegateType">The specified delegate type.</param>
        /// <param name="proxyDelegate">The specified proxy method.</param>
        /// <returns>An instance of delegate type.</returns>
        internal static Delegate ConvertToProxyDelegate(Type delegateType, Func<object[], object> proxyDelegate)
        {
            var proxyMethod = Expression.Constant(proxyDelegate, typeof(Func<object[], object>));
            var methodInfo = delegateType.GetMethod("Invoke");
            var parameters = methodInfo
                .GetParameters()
                .Select(parameterInfo => Expression.Parameter(parameterInfo.ParameterType, parameterInfo.Name))
                .ToList();

            var variables = new List<ParameterExpression>();
            var expressions = new List<Expression>();
            var arrayVar = Expression.Variable(typeof(object).MakeArrayType());

            variables.Add(arrayVar);
            var newArrayExpression = Expression.NewArrayInit(typeof(object),
                                                parameters.Select(expression => Expression.Convert(expression, typeof(object))).ToArray());
            expressions.Add(Expression.Assign(arrayVar, newArrayExpression));

            Expression methodCallExpression = Expression.Call(proxyMethod, typeof(Func<object[], object>).GetMethod("Invoke"), arrayVar);
            if (methodInfo.ReturnType != typeof(void))
                methodCallExpression = Expression.Convert(methodCallExpression, methodInfo.ReturnType);
            bool isHaveRef = parameters.Any(expression => expression.IsByRef);
            if (isHaveRef)
            {
                ParameterExpression result = null;
                if (methodInfo.ReturnType != typeof(void))
                {
                    result = Expression.Variable(methodInfo.ReturnType);
                    methodCallExpression = Expression.Assign(result, methodCallExpression);
                    variables.Add(result);
                }
                expressions.Add(methodCallExpression);
                for (int index = 0; index < parameters.Count; index++)
                {
                    var parameterExpression = parameters[index];
                    if (!parameterExpression.IsByRef) continue;
                    var arrayAccess = Expression.ArrayAccess(arrayVar, Expression.Constant(index));
                    expressions.Add(Expression.Assign(parameterExpression, Expression.Convert(arrayAccess, parameterExpression.Type)));
                }
                if (result != null)
                    expressions.Add(result);
            }
            else
                expressions.Add(methodCallExpression);
            return Expression.Lambda(delegateType, Expression.Block(variables, expressions), parameters).Compile();
        }

        #endregion

        #region Private methods

        private static WeakEventInfo GetWeakEventInfo(Delegate @delegate)
        {
            if (@delegate.Target == null)
                return null;
#if !NETFX_CORE
#if WINDOWS_PHONE
            if (!@delegate.Target.GetType().FullName.Equals("System.Runtime.CompilerServices.Closure"))
                return null;
            dynamic closure = @delegate.Target;
            IEnumerable<object> constants = closure.Constants;
#else
            var closure = @delegate.Target as Closure;
            if (closure == null)
                return null;
            IEnumerable<object> constants = closure.Constants;
#endif
#else
            if (!@delegate.Target.GetType().FullName.Equals("System.Runtime.CompilerServices.Closure"))
                return null;
            dynamic closure = @delegate.Target;
            IEnumerable<object> constants = closure.Constants;
#endif
            if (constants == null)
                return null;
            foreach (var constant in constants)
            {
                var weakEventInfo = constant as WeakEventInfo;
                if (weakEventInfo != null)
                    return weakEventInfo;
            }
            return null;
        }

        private static ActivatorDelegate CreateActivatorMethod(ConstructorInfo constructorInfo)
        {
            ParameterExpression parameterExpression;
            Expression[] expressions = GetParametersExpression(constructorInfo, out parameterExpression);
            Expression newExpression = Expression.New(constructorInfo, expressions);
#if NETFX_CORE
            if (constructorInfo.DeclaringType.GetTypeInfo().IsValueType)
#else
            if (constructorInfo.DeclaringType.IsValueType)
#endif
                newExpression = Expression.Convert(newExpression, typeof(object));
            return Expression.Lambda<ActivatorDelegate>(newExpression, parameterExpression).Compile();
        }

        private static TResult CreateMethodInvoke<TTarget, TResult>(MethodInfo methodInfo, Type targetType)
        {
            Type paramTargetType = typeof(TTarget);
            Type declaringType = methodInfo.DeclaringType;
#if NETFX_CORE
            var isTvalue = paramTargetType.GetTypeInfo().IsValueType;
            var isValueDeclaring = declaringType.GetTypeInfo().IsValueType;
#else
            bool isTvalue = paramTargetType.IsValueType;
            bool isValueDeclaring = declaringType.IsValueType;
#endif
            if (!targetType.IsByRef && isValueDeclaring && !isTvalue)
                throw new InvalidOperationException(
                    "It is impossible to call method to value type passed is not a reference. Use the version of the method to pass a parameter by reference.");
            bool isValue = isValueDeclaring && !isTvalue;
            bool isVoid = methodInfo.ReturnType == typeof(void);

            ParameterExpression targetExp = Expression.Parameter(targetType, "target");
            ParameterExpression parameterExpression;
            IList<ParameterExpression> localVariables;
            IList<Expression> assignRefParametersBefore;
            IList<Expression> assignRefParametersAfter;
            Expression[] expressions = GetParametersExpressionForMethod(methodInfo, out parameterExpression,
                                                                        out localVariables,
                                                                        out assignRefParametersBefore,
                                                                        out assignRefParametersAfter);
            Expression methodCallExpression;
            ParameterExpression result = Expression.Variable(typeof(object));
            localVariables.Add(result);
            if (methodInfo.IsStatic)
                methodCallExpression = Expression.Call(null, methodInfo, expressions);
            else
            {
                if (paramTargetType == declaringType)
                    methodCallExpression = Expression.Call(targetExp, methodInfo, expressions);
                else
                {
                    if (isValue)
                    {
                        ParameterExpression unboxInst = Expression.Variable(declaringType);
                        localVariables.Add(unboxInst);
                        Expression call = Expression.Call(unboxInst, methodInfo, expressions);
                        if (!isVoid)
                            call = Expression.Assign(result, Expression.Convert(call, typeof(object)));
                        methodCallExpression =
                            Expression.Block(
                                Expression.Assign(unboxInst, Expression.Convert(targetExp, declaringType)), call,
                                Expression.Assign(targetExp, Expression.Convert(unboxInst, typeof(object))));
                    }
                    else
                        methodCallExpression = Expression.Call(Expression.Convert(targetExp, declaringType), methodInfo,
                                                               expressions);
                }
            }
            var body = new List<Expression>();
            if (isVoid)
                body.Add(Expression.Assign(result, Expression.Constant(null)));
            else if (methodCallExpression is MethodCallExpression)
                methodCallExpression = Expression.Assign(result,
                                                         Expression.Convert(methodCallExpression, typeof(object)));

            body.AddRange(assignRefParametersBefore);
            body.Add(methodCallExpression);
            body.AddRange(assignRefParametersAfter);
            body.Add(result);
            methodCallExpression = Expression.Block(localVariables, body);

            return Expression
                .Lambda<TResult>(Expression.Convert(methodCallExpression, typeof(object)), targetExp,
                                 parameterExpression)
                .Compile();
        }

        private static TResult CreateFieldSetter<TTarget, TResult>(FieldInfo fieldInfo, Type targetType)
        {
            if (fieldInfo.IsInitOnly)
                throw new NotSupportedException("The read-only fields are not supported.");
            Type paramTargetType = typeof(TTarget);
            Type declaringType = fieldInfo.DeclaringType;

#if NETFX_CORE
            var isTvalue = paramTargetType.GetTypeInfo().IsValueType;
            var isValueDeclaring = declaringType.GetTypeInfo().IsValueType;
#else
            bool isTvalue = paramTargetType.IsValueType;
            bool isValueDeclaring = declaringType.IsValueType;
#endif
            if (!targetType.IsByRef && isValueDeclaring && !isTvalue)
                throw new InvalidOperationException(
                    "It is impossible to assign a field to value type passed is not a reference. Use the version of the method to pass a parameter by reference.");
            bool isValue = isValueDeclaring && !isTvalue;

            ParameterExpression targetExp = Expression.Parameter(targetType, "target");
            ParameterExpression valueExp = Expression.Parameter(typeof(object), "value");
            UnaryExpression convertedValue = Expression.Convert(valueExp, fieldInfo.FieldType);
            Expression bodyExpression;
            if (fieldInfo.IsStatic)
                bodyExpression = Expression.Assign(Expression.Field(null, fieldInfo), convertedValue);
            else
            {
                if (paramTargetType != declaringType)
                {
                    if (isValue)
                    {
                        ParameterExpression variable = Expression.Variable(declaringType);
                        BinaryExpression assign = Expression.Assign(variable,
                                                                    Expression.Convert(targetExp, declaringType));
                        bodyExpression = Expression.Block(new[] { variable }, assign,
                                                          Expression.Assign(Expression.Field(variable, fieldInfo),
                                                                            convertedValue),
                                                          Expression.Assign(targetExp,
                                                                            Expression.Convert(variable, typeof(object))));
                    }
                    else
                        bodyExpression =
                            Expression.Assign(
                                Expression.Field(Expression.Convert(targetExp, declaringType), fieldInfo),
                                convertedValue);
                }
                else
                    bodyExpression = Expression.Assign(Expression.Field(targetExp, fieldInfo), convertedValue);
            }
            return Expression.Lambda<TResult>(bodyExpression, targetExp, valueExp).Compile();
        }

        private static TResult CreateFieldGetter<TTarget, TResult>(FieldInfo fieldInfo, Type targetType, Type resultType)
        {
            ParameterExpression targetExp = Expression.Parameter(targetType, "target");
            MemberExpression fieldExp;
            if (fieldInfo.IsStatic)
                fieldExp = Expression.Field(null, fieldInfo);
            else
            {
                if (typeof(TTarget) != fieldInfo.DeclaringType)
                    fieldExp = Expression.Field(Expression.Convert(targetExp, fieldInfo.DeclaringType),
                                                fieldInfo);
                else
                    fieldExp = Expression.Field(targetExp, fieldInfo);
            }
            return Expression.Lambda<TResult>(Expression.Convert(fieldExp, resultType), targetExp).Compile();
        }

        private static TResult CreatePropertySetter<TTarget, TResult>(PropertyInfo propertyInfo, Type targetType)
        {
            Type paramTargetType = typeof(TTarget);
            Type declaringType = propertyInfo.DeclaringType;

#if NETFX_CORE
            var isTvalue = paramTargetType.GetTypeInfo().IsValueType;
            var isValueDeclaring = propertyInfo.DeclaringType.GetTypeInfo().IsValueType;
#else
            bool isTvalue = paramTargetType.IsValueType;
            bool isValueDeclaring = declaringType.IsValueType;
#endif
            if (!targetType.IsByRef && isValueDeclaring && !isTvalue)
                throw new InvalidOperationException(
                    "It is impossible to assign a property to value type passed is not a reference. Use the version of the method to pass a parameter by reference.");
            bool isValue = isValueDeclaring && !isTvalue;

            if (!propertyInfo.CanWrite)
                throw new ArgumentException(string.Format("The property {0} in type {1} not have setter.",
                                                          propertyInfo.PropertyType, InjectorUtilsInternal.FormatType(propertyInfo.DeclaringType)));

            ParameterExpression targetExp = Expression.Parameter(targetType, "target");
            ParameterExpression valueExp = Expression.Parameter(typeof(object), "value");
            UnaryExpression convertedValue = Expression.Convert(valueExp, propertyInfo.PropertyType);

            Expression bodyExpression;
            if (propertyInfo.GetSetMethod(true).IsStatic)
                bodyExpression = Expression.Assign(Expression.Property(null, propertyInfo), convertedValue);
            else
            {
                if (paramTargetType != declaringType)
                {
                    if (isValue)
                    {
                        ParameterExpression variable = Expression.Variable(declaringType);
                        BinaryExpression assign = Expression.Assign(variable,
                                                                    Expression.Convert(targetExp, declaringType));
                        bodyExpression = Expression.Block(new[] { variable },
                                                          assign,
                                                          Expression.Assign(
                                                              Expression.Property(variable, propertyInfo),
                                                              convertedValue),
                                                          Expression.Assign(targetExp,
                                                                            Expression.Convert(variable, typeof(object))));
                    }
                    else
                        bodyExpression =
                            Expression.Assign(
                                Expression.Property(Expression.Convert(targetExp, declaringType), propertyInfo),
                                convertedValue);
                }
                else
                    bodyExpression = Expression.Assign(Expression.Property(targetExp, propertyInfo), convertedValue);
            }
            return Expression.Lambda<TResult>(bodyExpression, targetExp, valueExp).Compile();
        }

        private static TResult CreatePropertyGetter<TTarget, TResult>(PropertyInfo propertyInfo, Type targetType, Type resultType)
        {
            if (!propertyInfo.CanRead)
                throw new ArgumentException(string.Format("The property {0} in type {1} not have getter.",
                                                          propertyInfo.PropertyType, InjectorUtilsInternal.FormatType(propertyInfo.DeclaringType)));

            ParameterExpression targetExp = Expression.Parameter(targetType, "target");
            MemberExpression propertyExp;
            if (propertyInfo.GetGetMethod(true).IsStatic)
                propertyExp = Expression.Property(null, propertyInfo);
            else
            {
                if (typeof(TTarget) != propertyInfo.DeclaringType)
                    propertyExp = Expression.Property(Expression.Convert(targetExp, propertyInfo.DeclaringType),
                                                      propertyInfo);
                else
                    propertyExp = Expression.Property(targetExp, propertyInfo);
            }
            return Expression.Lambda<TResult>(Expression.Convert(propertyExp, resultType), targetExp).Compile();
        }

        private static Expression[] GetParametersExpression(MethodBase methodBase,
                                                            out ParameterExpression parameterExpression)
        {
            ParameterInfo[] paramsInfo = methodBase.GetParameters();
            //create a single param of type object[]
            parameterExpression = Expression.Parameter(typeof(object[]), "args");
            var argsExp = new Expression[paramsInfo.Length];

            //pick each arg from the params array 
            //and create a typed expression of them
            for (int i = 0; i < paramsInfo.Length; i++)
            {
                Expression index = Expression.Constant(i);
                Type paramType = paramsInfo[i].ParameterType;
                Expression paramAccessorExp = Expression.ArrayIndex(parameterExpression, index);
                Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType);
                argsExp[i] = paramCastExp;
            }
            return argsExp;
        }

        private static Expression[] GetParametersExpressionForMethod(MethodBase methodBase,
                                                                     out ParameterExpression parameterExpression,
                                                                     out IList<ParameterExpression> localVariables,
                                                                     out IList<Expression> assignRefParametersBefore,
                                                                     out IList<Expression> assignRefParametersAfter)
        {
            ParameterInfo[] paramsInfo = methodBase.GetParameters();
            //create a single param of type object[]
            parameterExpression = Expression.Parameter(typeof(object[]), "args");
            localVariables = new List<ParameterExpression>();
            assignRefParametersAfter = new List<Expression>();
            assignRefParametersBefore = new List<Expression>();
            var argsExp = new Expression[paramsInfo.Length];

            //pick each arg from the params array 
            //and create a typed expression of them
            for (int i = 0; i < paramsInfo.Length; i++)
            {
                Expression index = Expression.Constant(i);
                Type paramType = paramsInfo[i].ParameterType;

                Expression paramAccessorExp = Expression.ArrayIndex(parameterExpression, index);
                if (paramType.IsByRef)
                {
                    ParameterExpression variable = Expression.Variable(paramType.GetElementType());
                    assignRefParametersBefore.Add(Expression.Assign(variable,
                                                                    Expression.Convert(paramAccessorExp, variable.Type)));
                    localVariables.Add(variable);
                    argsExp[i] = variable;
                    assignRefParametersAfter.Add(Expression.Assign(Expression.ArrayAccess(parameterExpression, index),
                                                                   Expression.Convert(variable, typeof(object))));
                    continue;
                }

                Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType);
                argsExp[i] = paramCastExp;
            }
            return argsExp;
        }

        #endregion
    }
}